package org.yaac.server.service;

import java.util.logging.Logger;

import javax.inject.Inject;

import org.antlr.runtime.RecognitionException;
import org.yaac.client.service.EGQLService;
import org.yaac.server.delegate.UserServiceDelegate;
import org.yaac.server.egql.Command;
import org.yaac.server.egql.EGQLUtil;
import org.yaac.server.egql.InsertItem;
import org.yaac.server.egql.InsertStatement;
import org.yaac.server.egql.Statement;
import org.yaac.server.egql.exception.EGQLException;
import org.yaac.server.egql.processor.ProcessContext;
import org.yaac.server.egql.processor.ProcessData.ProcessDataRecord;
import org.yaac.server.egql.processor.ProcessorManager;
import org.yaac.server.util.DatastoreUtil;
import org.yaac.shared.YaacException;
import org.yaac.shared.egql.Request;
import org.yaac.shared.egql.Response;
import org.yaac.shared.egql.ResponseStatus;

import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.tools.pipeline.PipelineService;

/**
 * @author Max Zhu (thebbsky@gmail.com)
 *
 */
public class EGQLServiceImpl implements EGQLService {

	/**
	 * 
	 */
	private final Logger logger;
	
	private final PipelineService pipeline;
	
	private final UserServiceDelegate userService;
	
	@Inject
	EGQLServiceImpl(Logger logger, 
			PipelineService pipeline, 
			UserServiceDelegate userService) {
		super();
		this.logger = logger;
		this.pipeline = pipeline;
		this.userService = userService;
	}

	@Override
	public Response execute(Request req) {
		try {
			Command cmd = EGQLUtil.parser(req.getCommand()).command().cmd;
			
			// validate all statements
			for (Statement stmt : cmd.getStatements()) {
				stmt.validate();
			}
			
			// validation passed, kick off all statements
			Response resp = new Response();
			for (Statement stmt : cmd.getStatements()) {
				
				// TODO : how to handle exceptions for simple statement?
				if (stmt.isSimpleStatement()) {
					if (stmt instanceof InsertStatement) {
						InsertStatement insertStmt = (InsertStatement) stmt;
						resp.getImmediateStmts().add(insertStmt.getRawStatement());
						executeSimpleInsert(insertStmt);	
					} else {
						// TODO supports other simple statements	
					}
				} else {
					ProcessContext context = new ProcessContext();
					context.setClientId(userService.getCurrentUser().getEmail());
					String pipelineId = pipeline.startNewPipeline(new ProcessorManager(), stmt.generateProcessors(), context);
					
					resp.add(pipelineId, stmt.getRawStatement());
					logger.info("started pipeline " + pipelineId + " for query: " + stmt.getRawStatement());	
				}
			}
			
			resp.setStatus(ResponseStatus.SUCCESS);
			return resp;
		} catch (RecognitionException e) {	// syntax error (should not happen as ANTLR will always try to recover from error)
			Response response = new Response();
			response.setStatus(ResponseStatus.SYNTAX_ERROR);
			response.setMessage(e.getMessage());
			return response;
		} catch (YaacException e) {	// syntax error (throw from EGQLParser.displayRecognitionError )
			Response response = new Response();
			response.setStatus(ResponseStatus.SYNTAX_ERROR);
			response.setMessage(e.getErrorMsg());			
			return response;
		} catch (EGQLException e) {	// logical exception
			Response response = new Response();
			response.setStatus(ResponseStatus.LOGICAL_ERROR);
			response.setErrorCode(e.getErrorCode());
			return response;
		}
	}
	
	private void executeSimpleInsert(InsertStatement stmt) {
		Entity e = null;
		
		InsertItem keyItem = stmt.keyItem();
		if (keyItem == null) {
			e = new Entity(stmt.getKind());
		} else {
			e = new Entity((Key) keyItem.getE().evaluate((ProcessDataRecord)null).getPayload());	// it should return a key
		}
		
		for(InsertItem item : stmt.propertyItems()) {
			Object val = item.getE().evaluate((ProcessDataRecord)null).getPayload();	// evaluation type
			val = DatastoreUtil.toDatastoreType(val);	// datastore type
			
			if (item.getIndexed()) {
				e.setProperty(item.getIdentity(), val);
			} else {
				e.setUnindexedProperty(item.getIdentity(), val);
			}
		}
		
		DatastoreServiceFactory.getAsyncDatastoreService().put(e);
	}
}
